package twoD.hofem.Q;

import inf.jlinalg.GeneralMatrixLSESolver;
import inf.jlinalg.ILSESolver;
import inf.jlinalg.IMatrix;
import inf.jlinalg.IMatrixReadOnly;
import inf.jlinalg.QuadraticMatrixInfo;
import inf.jlinalg.SolveFailedException;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

/**
 * The Model manages all objects involved in the problem (Elements, Loads and
 * the Mesh.)
 * 
 * @author Team5
 * 
 */
public class Model {

	private ArrayList<Element> elementsList = new ArrayList<Element>();
	private ArrayList<Load> loadsList = new ArrayList<Load>();
	private Mesh mesh_ = new Mesh();
	private Set<ModelObserver> observers_ = new HashSet<ModelObserver>();

	public Element addElement(Element e) throws IllegalArgumentException {
		elementsList.add(e);

		if (e.countFields() != elementsList.get(0).countFields()) {
			throw new IllegalArgumentException(
					"The elements are not of the same type");
		}
		return e;

	}

	public Load addLoad(Load l) {
		loadsList.add(l);
		return l;

	}

	public int countElements() {
		return elementsList.size();

	}

	public int countLoads() {
		return loadsList.size();

	}

	/**
	 * Returns the number of fields d involved in the problem.
	 * 
	 */
	public int countFields() {

		if (elementsList.size() > 0) {

			return elementsList.get(0).countFields();
		}

		else
			return 0;
	}

	public Element getElement(int idx) {
		return elementsList.get(idx);
	}

	public Load getLoad(int idx) {
		return loadsList.get(idx);
	}

	public int getfieldId(int idx) {

		return elementsList.get(1).getFieldId(idx);

	}

	public Mesh getMesh() {
		return mesh_;

	}

	/**
	 * Computes the approximate solution. We use the inf.jlinalg package to
	 * solve the system of linear equations.
	 * 
	 */

	public void solve() {

		getMesh().setD(countFields());

		getMesh().enumerateDofs();

		// size of our matrix
		int neq = getMesh().countDofs();

		// create the solver object
		// ILSESolver solver = new GeneralMatrixLSESolver();
		ILSESolver solver = new GeneralMatrixLSESolver(
				GeneralMatrixLSESolver.SOLVER_IMPL_JAVA);

		// info object for coefficient matrix " Global stiffness matrix"
		QuadraticMatrixInfo aInfo = solver.getAInfo();

		// get coefficient matrix " Global stiffness matrix"
		IMatrix a = solver.getA();

		// right hand side " Global load vector"
		double[] b = new double[neq];

		aInfo.setSize(neq);

		// initialize solver
		solver.initialize();

		// Assembly of the global load vector
		for (int i = 0; i < loadsList.size(); i++) {

			double[] r = loadsList.get(i).computeR();
			int dofs = loadsList.get(i).getMeshPart().countDofs();

			for (int j = 0; j < dofs; j++) {
				int[] tArray = loadsList.get(i).getMeshPart().getTArray();
				if (tArray[j] != -1) {
					b[tArray[j]] += r[j];
				}
			}
		}

		// Assembly of the global stiffness matrix
		IMatrixReadOnly k;
		for (int i = 0; i < elementsList.size(); i++) {

			k = elementsList.get(i).computeK();
			int dim = k.getColumnCount();
			for (int j = 0; j < dim; j++) {
				for (int m = 0; m < dim; m++) {

					int[] tArray = elementsList.get(i).getMeshPart()
							.getTArray();
					if ((tArray[j] != -1) && (tArray[m] != -1)) {
						a.add(tArray[j], tArray[m], k.get(j, m));
					}

				}
			}
		}

		// after calling solve, b contains the solution
		try {
			solver.solve(b);
		} catch (SolveFailedException e) {
			System.out.println("Solve failed: " + e.getMessage());
		}

		// Pass the approximate solution (u which is b) to the mesh
		getMesh().setUGlobal(b);

		// notify observers
		fireSolvedEvent();
	}

	/**
	 * Adds the specified observer.
	 * 
	 * @param o
	 *            observer to add
	 */
	public void addObserver(ModelObserver o) {
		observers_.add(o);
	}

	/**
	 * Notify observers that the solution data changed.
	 */
	public void fireSolvedEvent() {
		for (ModelObserver o : observers_) {
			o.solutionChanged(this);
		}
	}

	public void removeObserver(ModelObserver o) {
		observers_.remove(o);
	}
}
